--------------------
-- Image Cache
--------------------
image_cache = {}

-- check
function isInImageCache(image_url)
	if image_cache[image_url] ~= nil then
		return true
	end
	
	return false
end

-- get from cache
function getFromImageCache(image_url)
	dprint("ImageCache", image_url .. " loaded [cache].")
	return image_cache[image_url]
end

-- add to cache
function addToImageCache(image_url, image)
	dprint("ImageCache", image_url .. " loaded.")
	image_cache[image_url] = image
end

-- clear cache
function clearImageCache()
	dprint("ImageCache", "Cleared.")
	image_cache = {}
end

--------------------
-- Animation class
--------------------
Animation = {}
Animation.__index = Animation

function Animation.new(im, fw, fh, fq, int, loop)
	local an = {}
	setmetatable(an, Animation)
	
	-- variables for animation
	an.image_name = im
	an.frame_width = fw
	an.frame_height = fh
	an.frame_sequence = fq
	an.interval = int
	an.loop = loop
	an.length = #fq -- table.getn(fq)
	
	return an
end

--------------------
-- Gets animations id from frame sequence
--------------------
function Animation:getFrameId(index)
	return self.frame_sequence[index]
end

--------------------
-- Sprite class
-- All game objects should use this
-- If you use addAnimation, this class
-- becomes animatedSprite otherwise it's
-- just a sprite
--------------------
Sprite = {}
Sprite.__index = Sprite

function Sprite.new()
	local sp = {}
	setmetatable(sp, Sprite)
		
	-- variables for drawing
	sp.c_type = "sprite"
	sp.visible = true
	sp.active = true
	sp.origin_x = 0.0
	sp.origin_y = 0.0
	sp.scale_x = 1.0
	sp.scale_y = 1.0
	sp.angle = 0.0
	sp.alpha = 1.0
    sp.red = 1.0
    sp.blue = 1.0
    sp.green = 1.0

	sp.image = nil -- List of Images (love.graphics.newImage())
	sp.current_image = nil -- Currently used image (String).
	sp.anim_list = nil -- List of animations (Animation classes)
	sp.current_animation = nil
	sp.source_rect = nil -- List of Quads for Images (love.graphics.newQuad)
	
	-- variables for animation handling
	sp.elapsed_time = 0.0
	sp.current_frame_index = 0
	sp.animation_complete = false
	sp.pause = false
	sp.flip_x = false
	sp.flip_y = false
    
    -- flickering variables (8bit nes style) for hits
    sp.fl_time = 0.0 -- if 999 = unlimited
    sp.fl_interval = 0.0
    sp.fl_active = false
    sp.is_hit = false
    
    -- z index
    sp.z_index = 0

	return sp
end

--------------------
-- Get current image width
--------------------
function Sprite:getWidth()
    return self:getCurrentImage():getWidth()
end

--------------------
-- Get current image height
--------------------
function Sprite:getHeight()
    return self:getCurrentImage():getHeight()
end

--------------------
-- Replay animation
-- works only if animation is not looped
-- and if the previous animation is complete
--------------------
function Sprite:replayAnimation()
	local canim = self:getCurrentAnimation()
	if not canim.loop and self.animation_complete then
		self:resetAnimation()
	end
end

--------------------
-- Reset animation
--------------------
function Sprite:resetAnimation()
	self.current_frame_index = 0
	self.elapsed_time = 0.0
	self.animation_complete = false
end

--------------------
-- Play animation
--------------------
function Sprite:playAnimation(name)
    if name == self.current_animation then return end

	self.current_animation = name
	self:resumeAnimation()
	self:resetAnimation()
end

--------------------
-- Pause animation
--------------------
function Sprite:pauseAnimation()
	self.pause = true
end

--------------------
-- Resume animation
--------------------
function Sprite:resumeAnimation()
	self.pause = false
end

--------------------
-- Sets origin
--------------------
function Sprite:setOrigin(x, y)
	self.origin_x = x
	self.origin_y = y
end

--------------------
-- Gets origin
--------------------
function Sprite:getOrigin()
	return self.origin_x, self.origin_y
end

--------------------
-- Sets scale
--------------------
function Sprite:setScale(x, y)
	self.scale_x = x
	self.scale_y = y
end

--------------------
-- Gets scale
--------------------
function Sprite:getScale()
	return self.scale_x, self.scale_y
end

-------------------
-- Sets alpha
-------------------
function Sprite:setAlpha(value)
    self.alpha = value
end

-------------------
-- Sets color
-------------------
function Sprite:setColor(r, g, b, a)
    self.red = r
    self.green = g
    self.blue = b
    
    if a ~= nil then
        self:setAlpha(a)
    end
end

--------------------
-- Checks if current animation is the given animation
--------------------
function Sprite:isCurrentAnimation(name)
	return (self.current_animation == name)
end

--------------------
-- Gets current animation
--------------------
function Sprite:getCurrentAnimation()
	return self.anim_list[self.current_animation]
end

--------------------
-- Gets current source rect
--------------------
function Sprite:getCurrentSourceRect()
	if not self:isAnimation() then
		return self.source_rect[self.current_image]
	else
		return self.source_rect[self:getCurrentAnimation().image_name]
	end
end

--------------------
-- Loads image
--------------------
function Sprite:loadImage(image_name, image_url, smooth)
	-- Create list
	if self.image == nil then
		self.image = {} -- Create a new list for images
	end
	
	-- Create Quad list
	if self.source_rect == nil then
		self.source_rect = {}
	end

	local tmp = nil

	if isInImageCache(image_url) then
		--self.image[image_name] = getFromImageCache(image_name)
		tmp = getFromImageCache(image_url)
	else
		-- Load image
		-- self.image[image_name] = love.graphics.newImage(image_url)
		tmp = love.graphics.newImage(image_url)
		
		-- add to image cache
		addToImageCache(image_url, tmp)
	end
	
	-- Set filter mode
	-- self.image[image_name]:setFilter("nearest", "nearest")
    if smooth == nil or not smooth then
        tmp:setFilter("nearest", "nearest")
    end
    tmp:setWrap("repeat", "repeat")
	
	self.image[image_name] = tmp
	
	-- Sets loaded image to current if current image is nil
	if self.current_image == nil then
		self.current_image = image_name
	end
	
	-- Creates image's source rect
	local im = self.image[image_name]
	self.source_rect[image_name] = love.graphics.newQuad(0, 0, im:getWidth(), im:getHeight(),
		im:getWidth(), im:getHeight())
end

--------------------
-- Set current image
--------------------
function Sprite:setImage(image_name)
	self.current_image = image_name
end

--------------------
-- Get current image
--------------------
function Sprite:getCurrentImage()
	if not self:isAnimation() then
		return self.image[self.current_image]
	else
		return self.image[self:getCurrentAnimation().image_name]
	end
end

--------------------
-- Adds animations, add animations after image has loaded.
--------------------
function Sprite:addAnimation(name, image_name, fw, fh, fq, int, loop)
	if not self:isAnimation() then self.anim_list = {} end

	self.anim_list[name] = Animation.new(image_name, fw, fh, fq, int, loop)
	-- self.source_rect:setViewport(0, 0, fw, fh)
	
	if self.current_animation == nil then
		self.current_animation = name
		-- fix size
		self:setSourceRect(0, 0, fw, fh)
	end
end

--------------------
-- flips image/animation vertically or horizontally or both
--------------------
function Sprite:flip(x, y)
	self.flip_x = x
	self.flip_y = y
end

--------------------
-- Just checks if this Sprite is Sprite Animation
--------------------
function Sprite:isAnimation()
	return self.anim_list ~= nil
end

--------------------
-- Updates sprite
--------------------
function Sprite:update()
	self:updateAnimations()
    self:updateFlicker()
end

--------------------
-- Update flicker
--------------------
function Sprite:updateFlicker()
    if self.fl_active then
        self.is_hit = true
        if self.fl_time >= 0.0 then
            if self.fl_time ~= 999.0 then
                self.fl_time = self.fl_time - global_delta_time
            end
            
            self.fl_interval = self.fl_interval + global_delta_time
            if self.fl_interval > self.fl_int_target then
                self.visible = not self.visible
                self.fl_interval = 0.0
            end
        else
            self.visible = true
            self.fl_active = false
        end
    else
        self.is_hit = false
    end
end

--------------------
-- is flickering
--------------------
function Sprite:isFlickering()
    return self.fl_active
end

--------------------
-- Stops flickering
--------------------
function Sprite:stopFlickering()
    self.fl_active = false
    self.visible = true
end

--------------------
-- Play flicker
--------------------
function Sprite:playFlickering(time, interval)
    if not self.fl_active then
        self.fl_time = time
        self.fl_int_target = interval * 0.5 -- ex. 5.0sec -> 5 times
        self.fl_interval = 0.0
        self.fl_active = true
    end
end

--------------------
-- Updates animations if sprite has any
--------------------
function Sprite:updateAnimations()
	if self.image == nil then return end
	if self.current_animation == nil then return end
	if not self:isAnimation() then return end
	if self.pause then return end
	
	-- get current animation
	local anim = self:getCurrentAnimation()
	
	-- update frame
	self.elapsed_time = self.elapsed_time + global_delta_time
	if self.elapsed_time > anim.interval then
		self.elapsed_time = self.elapsed_time - anim.interval
		
		if anim.loop then
			self.current_frame_index = (self.current_frame_index + 1) % anim.length
		else
			self.current_frame_index = self.current_frame_index + 1
		end
	end
	
	-- Check wether animation is complete or not
	if self.current_frame_index == anim.length and not anim.loop then
		self.animation_complete = true
	end
	
	-- update source_rect
	self.current_frame_index = math.min(self.current_frame_index, anim.length - 1)
	local cfi = self.current_frame_index -- shorthand version of above
	local id = anim:getFrameId(cfi + 1)
    
    -- find source positions
    local tiles_width = self:getWidth() / anim.frame_width
    local x_pos = math.floor((id % tiles_width)) * anim.frame_width
    local y_pos = math.floor((id / tiles_width)) * anim.frame_height
 
	--self.source_rect[self:getCurrentAnimation().image_name]:setViewport(x_pos, 
    --    y_pos, anim.frame_width, anim.frame_height)
    self:setSourceRect(x_pos, y_pos, anim.frame_width, anim.frame_height)
end

--------------------
-- Set source rect
--------------------
function Sprite:setSourceRect(x, y, width, height)
    if self:isAnimation() then
        self.source_rect[self:getCurrentAnimation().image_name]:setViewport(x, y, width, height)
    else
        self.source_rect[self.current_image]:setViewport(x, y, width, height)
    end
end

-----------------
-- Drawing function
-- Draws sprite
-----------------
function Sprite:draw(x, y)
	if self.image == nil then return end
    if not self.visible then return end
    if x == nil then x = 0 end
    if y == nil then y = 0 end

	love.graphics.push()
	love.graphics.translate(x, y)
	love.graphics.scale(math.abs(self.scale_x), math.abs(self.scale_y))
	love.graphics.rotate(toRadians(self.angle))
	love.graphics.translate(-self.origin_x, -self.origin_y)
	
	-- flip
	local sx, sy, sw, sh = self:getCurrentSourceRect():getViewport()
	if self.flip_x and not self.flip_y then
		love.graphics.scale(-1.0, 1.0)
		love.graphics.translate(-sw, 0)
	elseif not self.flip_x and self.flip_y then
		love.graphics.scale(1.0, -1.0)
		love.graphics.translate(0, -sh)
	elseif self.flip_x and self.flip_y then
		love.graphics.scale(-1.0, -1.0)
		love.graphics.translate(-sw, -sh)
	end
	
	love.graphics.setColor(self.red * 255, self.green * 255, self.blue * 255, self.alpha * 255)
	love.graphics.drawq(self:getCurrentImage(), self:getCurrentSourceRect(), 0, 0)
	love.graphics.setColor(255, 255, 255, 255)
	love.graphics.pop()
end

-----------------
-- Drawing function with Camera
-- Draws sprite with camera
-----------------
--function Sprite:drawByCamera(camera)
--    if not self.visible then return end

--	local old_x, old_y = self:getPosition()
--	local new_x, new_y = camera:getRenderPosition(self.x, self.y)
	
--	self:setPosition(new_x, new_y)
--	self:draw()
--	self:setPosition(old_x, old_y)
--end
